/*
 * Copyright 2008, Freescale Semiconductor, Inc
 * Andy Fleming
 *
 * Based vaguely on the Linux code
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

#include <config.h>
#include <common.h>
#include <command.h>
#include <mmc.h>
#include <part.h>
#include <malloc.h>
#include <linux/list.h>
#include <div64.h>
#include <oem_func.h>
#include "comman.h"

static struct list_head mmc_devices;
struct mmc *find_mmc_device(int dev_num)
{
	struct mmc *m;
	struct list_head *entry;

	list_for_each(entry, &mmc_devices) {
		m = list_entry(entry, struct mmc, link);

		if (m->block_dev.dev == dev_num)
			return m;
	}

	printf("MMC Device %d not found\n", dev_num);

	return NULL;
}
block_dev_desc_t *mmc_get_dev(int dev)
{
	struct mmc *mmc = find_mmc_device(dev);

	return mmc ? &mmc->block_dev : NULL;
}

void deisplay_card_info(struct mmc *mmc)
{

}
int mmc_init(struct mmc *mmc)
{
	int err;
	if(mmc->initflag==MMC_INITIATED){          //&& (!mmc->Wflag)){
//		mmc->mmc_reset_host(mmc);
//		printf("This card has been initated.\n");
//	      	return 0;
	}
	err = mmc->init(mmc);

	if (err){
		//printf("Error init 1: mmc error 0x%x",err);
		return err;
	}
	mmc_set_bus_width(mmc, 1);
	mmc_set_clock(mmc, 1);

	/* Reset the Card */
	err = mmc_go_idle(mmc);
	if (err){
		printf("Error init 2: mmc error 0x%x\n",err);
		return err;
	}

	/* Test for SD version 2 */
	err = mmc_send_if_cond(mmc);

	/* Now try to get the SD card's operating condition */
	err = sd_send_op_cond(mmc);
	/* If the command timed out, we check for an MMC card */
	if (err == TIMEOUT) {
		err = mmc_send_op_cond(mmc);
		if (err) {
			printf("Card did not respond to voltage select!\n");
			return UNUSABLE_ERR;
		}
		err= mmc_startup(mmc);
	}else err= sd_startup(mmc);
	if(err) return err;
#ifdef DEBUG
	deisplay_card_info(mmc);
#endif
	return 0;
	 
}

/*
 * CPU and board-specific MMC initializations.  Aliased function
 * signals caller to move on
 */
static int __def_mmc_init(bd_t *bis)
{
	return -1;
}

int cpu_mmc_init(bd_t *bis) __attribute__((weak, alias("__def_mmc_init")));
//int board_mmc_init(bd_t *bis) __attribute__((weak, alias("__def_mmc_init")));
extern int board_mmc_init(bd_t *bis);

void print_mmc_devices(char separator)
{
	struct mmc *m;
	struct list_head *entry;

	list_for_each(entry, &mmc_devices) {
		m = list_entry(entry, struct mmc, link);

		printf("%s: %d", m->name, m->block_dev.dev);

		if (entry->next != &mmc_devices)
			printf("%c ", separator);
	}

	printf("\n");
}

int mmc_initialize(bd_t *bis)
{
	INIT_LIST_HEAD (&mmc_devices);
//	cur_dev_num = 0;

	if (board_mmc_init(bis) < 0)
		cpu_mmc_init(bis);

	print_mmc_devices(',');

	return 0;
}
static ulong mmc_bwrite(int dev_num, ulong start, lbaint_t blkcnt, const void*src)
{
	struct mmc *mmc = find_mmc_device(dev_num);

	if (!mmc)
		return -1;
	
	return mmc_write(mmc, start , blkcnt , src);
}
#if 0
int mmc_read(struct mmc *mmc, u64 src, uchar *dst, int size)
{
	char *buffer;
	int i;
	int blklen = mmc->read_bl_len;
	int startblock = lldiv(src, mmc->read_bl_len);
	int endblock = lldiv(src + size - 1, mmc->read_bl_len);
	int err = 0;

	/* Make a buffer big enough to hold all the blocks we might read */
	buffer = malloc(blklen);

	if (!buffer) {
		printf("Could not allocate buffer for MMC read!\n");
		return -1;
	}

	/* We always do full block reads from the card */
	err = mmc_set_blocklen(mmc, mmc->read_bl_len);

	if (err)
		return err;

	for (i = startblock; i <= endblock; i++) {
		int segment_size;
		int offset;

		err = mmc_read_block(mmc, buffer, i);

		if (err)
			goto free_buffer;

		/*
		 * The first block may not be aligned, so we
		 * copy from the desired point in the block
		 */
		offset = (src & (blklen - 1));
		segment_size = MIN(blklen - offset, size);

		memcpy(dst, buffer + offset, segment_size);

		dst += segment_size;
		src += segment_size;
		size -= segment_size;
	}

free_buffer:
	free(buffer);

	return err;
}
#endif
static ulong mmc_bread(int dev_num, ulong start, lbaint_t blkcnt, void *dst)
{
	int err;
	int i=0,j=0,k=0;
	struct mmc *mmc = find_mmc_device(dev_num);

	if (!mmc)
		return 0;

	if(blkcnt==1){
		err = mmc_read_block(mmc,dst,start);

		if(err){
			printf("block read failed:%d\n",err);
			return 0;
		}
		else 
			return 1;
	}
	else if(blkcnt > 1)
	{
		i = blkcnt / 0x8000;
		j = blkcnt % 0x8000;		
	
		k = start; 	
		for (k = start;k < (i*0x8000)+start; k+=0x8000,dst += (mmc->read_bl_len * 0x8000))
		{
			err = mmc_read_multi(mmc,dst,k,0x8000);
			if(err)
			{
				printf("block read failed:%d\n",err);
				return (k*0x8000);
			}
		}
		if(j)
		{
			if(j >1)
			{
				err = mmc_read_multi(mmc,dst,k,j);	
				if(err)
				{
					printf("block read failed:%d\n",err); 
					return (i*0x8000);
				}
			}
			else if(j==1)
			{
				err = mmc_read_block(mmc,dst,k);
				if(err)
				{
					printf("block read failed:%d\n",err); 
					return (blkcnt -1);

				}
			}

		}		
		return blkcnt;
	}	

	/* Add by warits(to make gcc happy), 20/05/2010 */
	return 0;

}
int mmc_register(struct mmc *mmc)
{
	/* Setup the universal parts of the block interface just once */
	mmc->block_dev.if_type = IF_TYPE_MMC;
	mmc->block_dev.dev = mmc->index;//cur_dev_num++;
	mmc->block_dev.removable = 1;
	mmc->block_dev.block_read = mmc_bread;
	mmc->block_dev.block_write = mmc_bwrite;

	INIT_LIST_HEAD (&mmc->link);

	list_add_tail (&mmc->link, &mmc_devices);

	return 0;
}

